/*
 * Copyright (C), 2014-2016, org.esaydev
 * FileName: AbsWechatService.java
 * Author:   jian
 * Date:     2016年3月14日 下午6:56:48
 */
package org.easydev.wechat.servlet;

import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.easydev.wechat.common.util.MessageUtil;
import org.easydev.wechat.common.util.SignUtil;
import org.easydev.wechat.message.aes.AesException;
import org.easydev.wechat.message.aes.WXBizMsgCrypt;
import org.easydev.wechat.message.base.BaseMsg;
import org.easydev.wechat.message.base.MsgType;
import org.easydev.wechat.message.event.BaseEventMsg;
import org.easydev.wechat.message.event.ClickMenu;
import org.easydev.wechat.message.event.EventType;
import org.easydev.wechat.message.event.LocationSelectMenu;
import org.easydev.wechat.message.event.PicSysphotoMenu;
import org.easydev.wechat.message.event.ScancodePushMenu;
import org.easydev.wechat.message.event.ViewMenu;
import org.easydev.wechat.message.txt.TextMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson.JSONObject;

/**
 * <Description>微信请求处理 <br>
 * 
 * @author jian<br>
 */
public abstract class AbsWechatService {

    /**
     * LOGGER
     */
    private final static Logger LOGGER = LoggerFactory.getLogger(AbsWechatService.class);

    /**
     * 子类提供token用于绑定微信公众平台
     *
     * @return token值
     */
    protected abstract String getToken();

    /**
     * 公众号APPID，使用消息加密模式时用户自行设置
     *
     * @return 微信公众平台提供的appid
     */
    protected abstract String getAppId();

    /**
     * 加密的密钥，使用消息加密模式时用户自行设置
     *
     * @return 用户自定义的密钥
     */
    protected abstract String getAESKey();

    /**
     * Description: 默认的方法，有需要时可以覆写<br>
     * 
     * @param event <br>
     * @return <br>
     */
    protected BaseMsg handleTxtMsg(TextMessage message) {
        return null;
    }

    /**
     * Description: 默认处理事件的方法，有需要时可以覆写<br>
     * 
     * @param event <br>
     * @return <br>
     */
    protected BaseMsg handleDefaultEvent(BaseEventMsg event) {
        return null;
    }

    /**
     * Description: 处理扫码的方法，有需要时可以覆写<br>
     * 
     * @param event <br>
     * @return <br>
     */
    protected BaseMsg handleQrCodeEvent(BaseEventMsg event) {
        return handleDefaultEvent(event);
    }

    /**
     * Description: 处理关注事件的方法，有需要时可以覆写<br>
     * 
     * @param event <br>
     * @return <br>
     */
    protected BaseMsg handleSubscribe(BaseEventMsg event) {
        return handleDefaultEvent(event);
    }

    /**
     * Description: 处理取消关注事件的方法，有需要时可以覆写<br>
     * 
     * @param event <br>
     * @return <br>
     */
    protected BaseMsg handleUnsubscribe(BaseEventMsg event) {
        return handleDefaultEvent(event);
    }

    /**
     * Description: 处理菜单点击事件的方法，有需要时可以覆写<br>
     * 
     * @param event <br>
     * @return <br>
     */
    protected BaseMsg handleMenuClickEvent(BaseEventMsg event) {
        return handleDefaultEvent(event);
    }

    /**
     * Description: 处理菜单view事件的方法，有需要时可以覆写<br>
     * 
     * @param event <br>
     * @return <br>
     */
    protected BaseMsg handleMenuViewEvent(BaseEventMsg event) {
        return handleDefaultEvent(event);
    }

    /**
     * Description: 处理扫码事件的方法，有需要时可以覆写<br>
     * 
     * @param event <br>
     * @return <br>
     */
    protected BaseMsg handleScanCodeEvent(BaseEventMsg event) {
        return handleDefaultEvent(event);
    }

    /**
     * Description: 处理发图事件的方法，有需要时可以覆写<br>
     * 
     * @param event <br>
     * @return <br>
     */
    protected BaseMsg handlePSendPicsInfoEvent(BaseEventMsg event) {
        return handleDefaultEvent(event);
    }

    /**
     * Description: 处理位置推送事件的方法，有需要时可以覆写<br>
     * 
     * @param event <br>
     * @return <br>
     */
    protected BaseMsg handleLocationEvent(BaseEventMsg event) {
        return handleDefaultEvent(event);
    }

    /**
     * Description: 服务器消息处理方法<br>
     * 
     * @param request <br>
     * @param response <br>
     * @return <br>
     */
    public String processRequest(HttpServletRequest request, HttpServletResponse response) {
        if (isLegal(request)) {
            return "Request is invalid!";
        }
        String result = "系统繁忙，请稍后再试！";
        Map<String, Object> reqMap = MessageUtil.parseXml(request, getToken(), getAppId(), getAESKey());
        String fromUserName = (String) reqMap.get("FromUserName");
        String toUserName = (String) reqMap.get("ToUserName");
        String msgType = (String) reqMap.get("MsgType");
        LOGGER.debug("收到消息,消息类型:{}", msgType);
        BaseMsg msg = null;
        JSONObject msgJson = (JSONObject) JSONObject.toJSON(reqMap);
        if (msgType.equals(MsgType.EVENT.getValue())) {
            String eventType = (String) reqMap.get("Event");
            if (eventType.equals(EventType.SCAN)) {
                msg = handleQrCodeEvent(JSONObject.toJavaObject(msgJson, ScancodePushMenu.class));
            }
            if (eventType.equals(EventType.SUBSCRIBE)) {
                msg = handleSubscribe(JSONObject.toJavaObject(msgJson, BaseEventMsg.class));
            } else if (eventType.equals(EventType.UNSUBSCRIBE)) {
                msg = handleUnsubscribe(JSONObject.toJavaObject(msgJson, BaseEventMsg.class));
            } else if (eventType.equals(EventType.CLICK)) {
                msg = handleMenuClickEvent(JSONObject.toJavaObject(msgJson, ClickMenu.class));
            } else if (eventType.equals(EventType.VIEW)) {
                msg = handleMenuViewEvent(JSONObject.toJavaObject(msgJson, ViewMenu.class));
            } else if (eventType.equals(EventType.LOCATION)) {
                msg = handleLocationEvent(JSONObject.toJavaObject(msgJson, LocationSelectMenu.class));
            } else if (EventType.SCANCODEPUSH.equals(eventType) || EventType.SCANCODEWAITMSG.equals(eventType)) {
                msg = handleScanCodeEvent(JSONObject.toJavaObject(msgJson, ScancodePushMenu.class));
            } else if (EventType.PICPHOTOORALBUM.equals(eventType) || EventType.PICSYSPHOTO.equals(eventType)
                    || EventType.PICWEIXIN.equals(eventType)) {
                msg = handlePSendPicsInfoEvent(JSONObject.toJavaObject(msgJson, PicSysphotoMenu.class));
            } else {
                LOGGER.warn("cannot deal with event type:{}", eventType);
                msg = handleDefaultEvent(JSONObject.toJavaObject(msgJson, BaseEventMsg.class));
            }
        } else if (msgType.equals(MsgType.TEXT.getValue())) {
            String content = (String) reqMap.get("Content");
            LOGGER.debug("文本消息内容:{}", content);
            msg = handleTxtMsg(JSONObject.toJavaObject(msgJson, TextMessage.class));
        } else if (msgType.equals(MsgType.IMAGE.getValue())) {
            // TODO
        } else if (msgType.equals(MsgType.VOICE.getValue())) {
            // TODO
        } else if (msgType.equals(MsgType.VIDEO.getValue())) {
            // TODO
        } else if (msgType.equals(MsgType.LOCATION.getValue())) {
            // TODO
        }
        if (null != msg) {
            msg.setFromUserName(toUserName);
            msg.setToUserName(fromUserName);
            if (StringUtils.isNotBlank(getAESKey())) {
                try {
                    WXBizMsgCrypt pc = new WXBizMsgCrypt(getToken(), getAESKey(), getAppId());
                    result = pc.encryptMsg(result, request.getParameter("timestamp"), request.getParameter("nonce"));
                    LOGGER.debug("加密后密文:{}", result);
                } catch (AesException e) {
                    LOGGER.error("加密异常", e);
                }
            }
        }
        return result;
    }

    /**
     * Description: 校验请求合法性<br>
     * 
     * @param request <br>
     * @return <br>
     */
    protected boolean isLegal(HttpServletRequest request) {
        String signature = request.getParameter("signature");
        String timestamp = request.getParameter("timestamp");
        String nonce = request.getParameter("nonce");
        return SignUtil.checkSignature(getToken(), signature, timestamp, nonce);
    }
}
